/* ****************************************************************************
 * Copyright (c) TCC - 2009 IME - USP. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. 
 * A copy of the license can be found in http://msdn.microsoft.com/en-us/library/dd228915.aspx. 
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the 
 * Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 * ***************************************************************************/

namespace SoundLibrary
{
    using System;
    using System.Diagnostics;

    internal sealed class Bitstream//// : BitstreamErrors
    {
        internal static sbyte INITIAL_SYNC = 0;
        internal static sbyte STRICT_SYNC = 1;
        private const int BUFFER_INT_SIZE = 433;
        private int[] framebuffer;
        private int framesize;
        private sbyte[] frame_bytes; // armazena os dados de um frame de um mp3
        private int wordpointer;
        private int bitindex;
        private int syncword;
        private bool single_ch_mode;
        private BackStream source;
        private Header header;
        private sbyte[] syncbuf;
        private Crc16[] crc;
        private int[] bitmask = new int[]
        {
            0, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF
        };

        #region Constructors
        internal Bitstream(BackStream in_Renamed)
        {
            this.InitBlock();
            if (in_Renamed == null)
            {
                throw new System.NullReferenceException("in");
            }

            this.source = in_Renamed; // ROB - fuck the SupportClass, let's roll our own. new SupportClass.BackInputStream(in_Renamed, 1024);

            this.CloseFrame();
        }
        #endregion

        #region Public Methods
        public int Get_bits(int number_of_bits)
        {
            int returnvalue = 0;
            int sum = this.bitindex + number_of_bits;

            if (this.wordpointer < 0)
            {
                this.wordpointer = 0;
            }

            if (sum <= 32)
            {
                returnvalue = SupportClass.URShift(this.framebuffer[this.wordpointer], (32 - sum)) & this.bitmask[number_of_bits];
                if ((this.bitindex += number_of_bits) == 32)
                {
                    this.bitindex = 0;
                    this.wordpointer++;
                }

                return returnvalue;
            }

            int right = this.framebuffer[this.wordpointer] & 0x0000FFFF;
            this.wordpointer++;
            int left = this.framebuffer[this.wordpointer] & (int)SupportClass.Identity(0xFFFF0000);
            returnvalue = (right << 16) & (int)SupportClass.Identity(0xFFFF0000) | SupportClass.URShift(left, 16) & 0x0000FFFF;
            returnvalue = SupportClass.URShift(returnvalue, 48 - sum);
            returnvalue &= this.bitmask[number_of_bits];
            this.bitindex = sum - 32;
            return returnvalue;
        }

        public void Close()
        {
            try
            {
                this.source.Close();
            }
            catch (System.IO.IOException)
            {
                ////throw newBitstreamException(decoder.BitstreamErrors_Fields.STREAM_ERROR, ex);
            }
        }

        public void UnreadFrame()
        {
            if (this.wordpointer == -1 && this.bitindex == -1 && (this.framesize > 0))
            {
                try
                {
                    this.source.UnRead(this.framesize);
                }
                catch (System.IO.IOException)
                {
                    ////throw newBitstreamException(decoder.BitstreamErrors_Fields.STREAM_ERROR);
                }
            }
        }

        public void CloseFrame()
        {
            this.framesize = -1;
            this.wordpointer = -1;
            this.bitindex = -1;
        }

        public bool IsSyncCurrentPosition(int syncmode)
        {
            int read = this.ReadBytes(this.syncbuf, 0, 4);
            int headerstring = ((this.syncbuf[0] << 24) & (int)SupportClass.Identity(0xFF000000)) | ((this.syncbuf[1] << 16) & 0x00FF0000) | ((this.syncbuf[2] << 8) & 0x0000FF00) | ((this.syncbuf[3] << 0) & 0x000000FF);

            try
            {
                this.source.UnRead(read);
            }
            catch (System.IO.IOException)
            {
            }

            bool sync = false;
            switch (read)
            {
                case 0:
                    Trace.WriteLine("0 bytes read == sync?", "Bitstream");
                    sync = true;
                    break;

                case 4:
                    sync = this.IsSyncMark(headerstring, syncmode, this.syncword);
                    break;
            }

            return sync;
        }

        public int ReadCheckedBits(int n)
        {
            return this.Get_bits(n);
        }

        public bool IsSyncMark(int headerstring, int syncmode, int word)
        {
            bool sync = false;

            if (syncmode == INITIAL_SYNC)
            {
                sync = (headerstring & 0xFFE00000) == 0xFFE00000;
            }
            else
            {
                sync = ((headerstring & 0xFFE00000) == 0xFFE00000)
                && (((headerstring & 0x000000C0) == 0x000000C0) == this.single_ch_mode);
            }

            if (sync)
            {
                sync = (SupportClass.URShift(headerstring, 10) & 3) != 3;
                if (!sync)
                {
                    Trace.WriteLine("INVALID SAMPLE RATE DETECTED", "Bitstream");
                }
            }

            if (sync)
            {
                sync = (SupportClass.URShift(headerstring, 17) & 3) != 0;
                if (!sync)
                {
                    Trace.WriteLine("INVALID LAYER DETECTED", "Bitstream");
                }
            }

            if (sync)
            {
                sync = (SupportClass.URShift(headerstring, 19) & 3) != 1;
                if (!sync)
                {
                    Console.WriteLine("INVALID VERSION DETECTED");
                }
            }

            return sync;
        }
        #endregion

        #region Internal Methods
        internal Header ReadFrame()
        {
            Header result = null;
            try
            {
                if (this.framesize == -1)
                {
                    this.header.read_header(this, this.crc);
                }

                result = this.header;
            }
            catch (Exception)
            {
            }

            return result;
        }

        internal int SyncHeader(sbyte syncmode)
        {
            bool sync;
            int headerstring;

            int bytesRead = this.ReadBytes(this.syncbuf, 0, 3);

            if (bytesRead != 3)
            {
                ////throw newBitstreamException(decoder.BitstreamErrors_Fields.STREAM_EOF, null);
            }

            headerstring = ((this.syncbuf[0] << 16) & 0x00FF0000) | ((this.syncbuf[1] << 8) & 0x0000FF00) | ((this.syncbuf[2] << 0) & 0x000000FF);

#if THROW_ON_SYNC_LOSS
t/DD: If we don't resync in a reasonable amount of time, 
throw an exception
int bytesSkipped = 0;
bool lostSyncYet = false;
#endif

            do
            {
                headerstring <<= 8;

                if (this.ReadBytes(this.syncbuf, 3, 1) != 1)
                {
                    ////throw newBitstreamException(BitstreamErrors_Fields.STREAM_EOF, null);
                }

                headerstring |= this.syncbuf[3] & 0x000000FF;

                sync = this.IsSyncMark(headerstring, syncmode, this.syncword);

#if THROW_ON_SYNC_LOSS
// Just for debugging -- if we lost sync, bitch
if (!sync && !lostSyncYet)
{
lostSyncYet = true;
Trace.WriteLine( "Lost Sync :(", "Bitstream" );
}

if (lostSyncYet && sync)
{
Trace.WriteLine( "Found Sync", "Bitstream" );
}


// If we haven't resynced within a frame (or so) give up and
// throw an exception. (Could try harder?)
++ bytesSkipped;
if ((bytesSkipped % 2048) == 0) // A paranoia check -- is the code hanging in a loop here?
{
Trace.WriteLine( "Sync still not found", "Bitstream" );
// throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, 
//                             null);
}
#endif
            }
            while (!sync);

            return headerstring;
        }

        internal void Read_frame_data(int bytesize)
        {
            this.ReadFully(this.frame_bytes, 0, bytesize);
            this.framesize = bytesize;
            this.wordpointer = -1;
            this.bitindex = -1;
        }

        internal void Parse_frame()
        {
            int b = 0;
            sbyte[] byteread = this.frame_bytes;
            int bytesize = this.framesize;

            for (int k = 0; k < bytesize; k = k + 4)
            {
                sbyte b0 = 0;
                sbyte b1 = 0;
                sbyte b2 = 0;
                sbyte b3 = 0;
                b0 = byteread[k];
                if (k + 1 < bytesize)
                {
                    b1 = byteread[k + 1];
                }

                if (k + 2 < bytesize)
                {
                    b2 = byteread[k + 2];
                }

                if (k + 3 < bytesize)
                {
                    b3 = byteread[k + 3];
                }

                this.framebuffer[b++] = ((b0 << 24) & (int)SupportClass.Identity(0xFF000000)) | ((b1 << 16) & 0x00FF0000) | ((b2 << 8) & 0x0000FF00) | (b3 & 0x000000FF);
            }

            this.wordpointer = 0;
            this.bitindex = 0;
        }

        internal void Set_syncword(int syncword0)
        {
            this.syncword = syncword0 & unchecked((int)0xFFFFFF3F);
            this.single_ch_mode = (syncword0 & 0x000000C0) == 0x000000C0;
        }

        #endregion

        #region Private Methods

        private void InitBlock()
        {
            this.crc = new Crc16[1];
            this.syncbuf = new sbyte[4];
            this.frame_bytes = new sbyte[BUFFER_INT_SIZE * 4];
            this.framebuffer = new int[BUFFER_INT_SIZE];
            this.header = new Header();
        }

        private void ReadFully(sbyte[] b, int offs, int len)
        {
            try
            {
                while (len > 0)
                {
                    int bytesread = this.source.Read(b, offs, len);
                    if (bytesread == -1 || bytesread == 0)
                    {
                        Trace.WriteLine("readFully -- returning success at EOF? (" + bytesread + ")", "Bitstream");
                        while (len-- > 0)
                        {
                            b[offs++] = 0;
                        }

                        break;
                        ////throw newBitstreamException(UNEXPECTED_EOF, new EOFException());
                    }

                    offs += bytesread;
                    len -= bytesread;
                }
            }
            catch (System.IO.IOException)
            {
                ////throw newBitstreamException(decoder.BitstreamErrors_Fields.STREAM_ERROR, ex);
            }
        }

        private int ReadBytes(sbyte[] b, int offs, int len)
        {
            int totalBytesRead = 0;
            try
            {
                while (len > 0)
                {
                    int bytesread = this.source.Read(b, offs, len);

                    if (bytesread == -1 || bytesread == 0)
                    {
                        break;
                    }

                    totalBytesRead += bytesread;
                    offs += bytesread;
                    len -= bytesread;
                }
            }
            catch (System.IO.IOException)
            {
                ////throw newBitstreamException(decoder.BitstreamErrors_Fields.STREAM_ERROR, ex);
            }

            return totalBytesRead;
        }

        #endregion
    }
}
